﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CNShapes;
using BobcatMultiTouch;
using CNPageEditor;
using CNPageViewer;
using BobcatUtility;

namespace CNPageEditor
{
    /// <summary>
    /// This control lets you edit and view shapes from a Page.
    /// It uses the PageViewerControl to show the shapes and uses the 
    /// MultiTouchInputClient to get input events.
    /// </summary><remarks>
    /// 
    /// Actions and registerActions()
    /// -----------------------------
    /// Actions that can be performed on the picture are embodies in classes that 
    /// derived from the Action base class. This allows us to use them with the 
    /// ActionManager which embodies the command-pattern for undo and redo.
    /// 
    /// You register actions in the registerActions() method. This will make them
    /// available via a toolbar.
    /// 
    /// Gestures and registerGestures()
    /// -------------------------------
    /// As well as performing actions, the editor keeps an eye out for various "gestures".
    /// These are input movements (which may be as simple as a double-click) that we
    /// interpret to mean things other than just (say) freehand drawing. If we detect a 
    /// gesture, we usually change from one action to another. 
    /// 
    /// You can register gesture-handlers (which derived from the base GestureHandler class)
    /// in the registerGestures() method. Gesture-handlers receive touch events before the 
    /// actions themselves and have the ability to interrupt actions and replace them
    /// with other actions.
    /// 
    /// Previous action type
    /// --------------------
    /// Sometimes when an action has been completed we want to perform the same
    /// action again. At other times we may want to revert to a different action.
    /// or example, after drawing a freehand shape, the default will be to draw
    /// another. But after dragging the picture around, the default will be to go
    /// back to doing what you did before you dragged it.
    /// 
    /// To do this, we store an optional previous-action-type (PAT). When an action
    /// has completed we need to decide which action to perform next. If the PAT is
    /// set up we revert to it. If it isn't, then we find the current selected action
    /// from the action toolbar.
    /// 
    /// </remarks>
    public partial class PageEditorControl : UserControl
    {
        #region Registration methods

        /// <summary>
        /// You should register all actions here. They will be made available
        /// in the actions toolbar.
        /// </summary>
        private void registerActions()
        {
            // Note: The first action registered is the default action, 
            //       i.e. the one selected by default when the control
            //       is first shown.
            registerAction(new Action_DrawFreehandShape());
            registerAction(new Action_DrawBox());
            registerAction(new Action_DrawArrow());
            registerAction(new Action_DrawEllipse());
            registerAction(new Action_AddText());
            registerAction(new Action_ShowPointer());
            registerAction(new Action_DragPicture());
            registerAction(new Action_MultiTouchZoomPicture());
            registerAction(new Action_ChooseColor());
        }

        /// <summary>
        /// You should register all gesture-handlers here.
        /// </summary>
        private void registerGestureHandlers()
        {
            registerGestureHandler(new GestureHandler_DragPicture());
            registerGestureHandler(new GestureHandler_MultiTouchZoomPicture());
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Returns a reference to the ShapeViewControl that shows the shapes.
        /// </summary>
        public PageViewerControl PageViewer
        {
            get { return pageViewerControl; }
        }

        /// <summary>
        /// Returns a reference to the MTIC managing the input for this control.
        /// </summary>
        public MultiTouchInputClient MTIC
        {
            get { return m_MTIC; }
        }

        /// <summary>
        /// Property to get or set the Page managed by this control
        /// </summary>
        public Page Page
        {
            get
            {
                return m_Page;
            }
            set
            {
                // We store our reference to the page...
                m_Page = value;

                // We give a reference to the shape-viewer so that it can show the data...
                pageViewerControl.Page = m_Page;

                // We reset the action-manager, as undo does not work accross
                // changes to the page...
                m_ActionManager = new ActionManager();
                m_ActiveAction = null;
                m_PreviousActionType = null;

                // We create the active action (this may have been chosen earlier, but not
                // set up as it needs the Page)...
                createActiveAction();
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public PageEditorControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Undoes the most recent action.
        /// </summary>
        public void undo()
        {
            m_ActionManager.undo();
        }

        /// <summary>
        /// Redoes the most recently undone action.
        /// </summary>
        public void redo()
        {
            m_ActionManager.redo();
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Called when this control is first loaded.
        /// </summary>
        private void PageEditorControl_Load(object sender, EventArgs e)
        {
            // We register for events from the MTIC...
            m_MTIC.initialize(this);
            m_MTIC.TouchDownEvent += onTouchDown;
            m_MTIC.TouchUpEvent += onTouchUp;
            m_MTIC.TouchMoveEvent += onTouchMove;

            // We register the available actions and we register for action-selected 
            // events from the action-toolbar...
            ctrlActionToolbar.ActionSelectedEvent += onActionSelected; // Note: this needs to come before registerActions() as otherwise we miss the initial action-selected event.
            registerActions();

            // We register the gesture-handlers...
            registerGestureHandlers();
        }

        /// <summary>
        /// We register one gesture handler, and hook up to its events.
        /// </summary>
        private void registerGestureHandler(GestureHandler gestureHandler)
        {
            gestureHandler.initialize(m_Page, this);
            m_GestureHandlers.Add(gestureHandler);
            gestureHandler.GestureDetectedEvent += onGestureDetected;
        }

        /// <summary>
        /// Registers one action.
        /// </summary>
        private void registerAction(CNPageEditor.Action action)
        {
            // We add it to the action toolbar...
            ctrlActionToolbar.addAction(action);
        }

        /// <summary>
        /// Called when a gesture has been detected.
        /// </summary>
        private void onGestureDetected(object sender, GestureHandler.Args e)
        {
            // We've detected a gesture, so we abort any current action...
            if (m_ActiveAction != null)
            {
				m_ActiveAction.deactivate();
                m_ActiveAction.ActionCompletedEvent -= onActionCompleted;
                m_ActiveAction.ActionCanceledEvent -= onActionCanceled;
                m_ActiveAction.undo();
            }

            // If we need to revert to the current action after this action has
            // completed, we store the current action...
            if (e.RevertToPreviousActionWhenComplete == true)
            {
                // We do not store the previous action if there is already one set up.
                // This is because it is possible for one gesture to interrupt the action
                // set up by a previous gesture. We only want to store the original underlying
                // action to revert to later, and not any other intermediate ones...
                if (m_ActiveAction != null && m_PreviousActionType == null)
                {
                    m_PreviousActionType = m_ActiveAction.GetType();
                }
            }

            // We set the action passed in with this event as the active action...
            ctrlActionToolbar.SelectedActionType = e.NewAction.GetType();
            m_ActiveAction = e.NewAction;
            m_ActiveAction.ActionCompletedEvent += onActionCompleted;
            m_ActiveAction.ActionCanceledEvent += onActionCanceled;
        }

        /// <summary>
        /// Called when a new action is selected from the action-toolbar.
        /// </summary>
        void onActionSelected(object sender, ActionToolbarControl.Args e)
        {
            // When a new action is selected, the previous active action has become
            // invalid. We want to make sure that it has undone anything it might have
            // done...
            if (m_ActiveAction != null)
            {
                m_ActiveAction.undo();
            }

            // And we create the new active action object...
            createActiveAction();
        }

        /// <summary>
        /// Called when the current action has completed.
        /// </summary>
        private void onActionCompleted(object sender, Action.Args e)
        {
            // We add the completed action to the undo list...
            if (m_ActiveAction.supportsUndo() == true)
            {
                m_ActionManager.addAction(m_ActiveAction);
            }

            // And we create the next action...
            createActiveAction();
        }

        /// <summary>
        /// Called if the current action was canceled by the user.
        /// </summary>
        private void onActionCanceled(object sender, Action.Args e)
        {
            // This is similar to onActionCompled (above) except that we don't
            // add the action to the undo list.

            // We create the next action...
            createActiveAction();
        }

        /// <summary>
        /// Creates the active action based on the action-type in the action-type stack.
        /// </summary>
        private void createActiveAction()
        {
            // We can only create the action if we have been fully set up...
            if (m_Page == null)
            {
                return;
            }

            // If there is a current active action, we stop observing it...
            if (m_ActiveAction != null)
            {
				m_ActiveAction.deactivate();
                m_ActiveAction.ActionCompletedEvent -= onActionCompleted;
                m_ActiveAction.ActionCanceledEvent -= onActionCanceled;
            }

            // We check if we need to revert to a previous action. If not we just use the
            // one selected in the toolbar...
            if (m_PreviousActionType != null)
            {
                ctrlActionToolbar.SelectedActionType = m_PreviousActionType;
                m_PreviousActionType = null;
            }
            Type actionType = ctrlActionToolbar.SelectedActionType;

            // We create the action and set it as the active action...
            Action previousAction = m_ActiveAction;
            m_ActiveAction = Utility.createInstance<Action>(actionType);
            
            // If we are repeating the previous action, we check if the action
            // is one that allows repeats...
            if (previousAction != null && previousAction.GetType() == m_ActiveAction.GetType())
            {
                // We are repeating the previous action. Does the action allow this?
                if (m_ActiveAction.isRepeatable() == false)
                {
                    // The action is not repeatable, so we cancel it...
                    m_ActiveAction = null;
                    return;
                }
            }

            // We set up the new action...
            m_ActiveAction.ActionCompletedEvent += onActionCompleted;
            m_ActiveAction.ActionCanceledEvent += onActionCanceled;
            m_ActiveAction.initialize(m_Page, this);

            // We notify any gestures that a new action has been started. (They may want to reset
            // their status and start checking for new gestures)...
            foreach (GestureHandler gestureHandler in m_GestureHandlers)
            {
                gestureHandler.onNewAction();
            }
        }

        /// <summary>
        /// Called when we get a touch-down event in this control.
        /// </summary>
        private void onTouchDown(object sender, TouchArgs e)
        {
            if (touchIsValid(e) == false) return;

            // We route the event to all gesture-handlers...
            foreach (GestureHandler gestureHandler in m_GestureHandlers)
            {
                gestureHandler.onTouchDown(e);
            }

            // We route the event to the active action...
            if (m_ActiveAction != null)
            {
                m_ActiveAction.onTouchDown(e);
            }
        }

        /// <summary>
        /// Called when we get a touch-move event.
        /// </summary>
        private void onTouchMove(object sender, TouchArgs e)
        {
            if (touchIsValid(e) == false) return;

            // We route the event to all gesture-handlers...
            foreach (GestureHandler gestureHandler in m_GestureHandlers)
            {
                gestureHandler.onTouchMove(e);
            }

            // We route the event to the active action...
            if (m_ActiveAction != null)
            {
                m_ActiveAction.onTouchMove(e);
            }
        }

        /// <summary>
        /// Called when we get a touch-up event.
        /// </summary>
        private void onTouchUp(object sender, TouchArgs e)
        {
            if (touchIsValid(e) == false) return;

            // We route the event to all gesture-handlers...
            foreach (GestureHandler gestureHandler in m_GestureHandlers)
            {
                gestureHandler.onTouchUp(e);
            }

            // We route the event to the active action...
            if (m_ActiveAction != null)
            {
                m_ActiveAction.onTouchUp(e);
            }
        }

        /// <summary>
        /// Checks if the touch passed in is in an area of the screen that is part of
        /// the drawing area of the control. This filters out touches that occur over
        /// the control toolbox.
        /// </summary>
        private bool touchIsValid(TouchArgs e)
        {
            // We check if the touch was over the control toolbox...
            if(ctrlActionToolbar.Bounds.Contains(e.ClientPosition))
            {
                return false;
            }
            return true;
        }
        
        #endregion

        #region Private data

        // The page that we are showing and editing...
        private Page m_Page = null;

        // The MTIC, which routes input events to the control...
        private MultiTouchInputClient m_MTIC = new MultiTouchInputClient();

        // The action manager stores recent actions for undo, redo etc...
        private ActionManager m_ActionManager = new ActionManager();

        // The current, active action...
        private CNPageEditor.Action m_ActiveAction = null;

        // The previous-action-type that we may revert to after an action...
        private Type m_PreviousActionType = null;

        // Collection of gesture-handlers.
        private IList<GestureHandler> m_GestureHandlers = new List<GestureHandler>();

        #endregion

    }
}
